/* 
 * [New BSD License (BSD 3-Clause License)]
 * Copyright (c) 2012, Max Bechtold
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 *     - Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     - Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     - Neither the name of the Rel2Xml Project nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
grammar FillTable;

options {
  language = Java;
}

// Make sure Lexer also finds its way into right package
@lexer::header
{
/* 
 * [New BSD License (BSD 3-Clause License)]
 * Copyright (c) 2012, Max Bechtold
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 *     - Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     - Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     - Neither the name of the Rel2Xml Project nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
  package maxbe.rel2xml.grammar;
}

@header
{
/* 
 * [New BSD License (BSD 3-Clause License)]
 * Copyright (c) 2012, Max Bechtold
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 *     - Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     - Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     - Neither the name of the Rel2Xml Project nor the
 *       names of its contributors may be used to endorse or promote products
 *       derived from this software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
  package maxbe.rel2xml.grammar;
  
  import maxbe.rel2xml.generator.Column;
  import maxbe.rel2xml.generator.Table;
  import maxbe.rel2xml.generator.Table.FillFactor;
  import maxbe.rel2xml.generator.docs.constraints.*;
}

@members
{
	private SchemaBuilder sb;
}

table_filling_list [SchemaBuilder sb]
@init 
{
  this.sb = $sb;
}
@after
{
  sb.checkContexts();
}
  :
  (table_filling)*
  ;

table_filling
  :
  'fill' 'table' t=table
  'with' filling_factor[$t.t]
  (column_filling_list[$t.t])?
  SEMICOLON
  ;
 
table returns [Table t]
  :
  ID
		           { $t = sb.getTable($ID.text); }
  ;
  
filling_factor [Table t]
@init
{
  int min = 0;
  int max = -1;
  int per = 1;
  String columnName = null;  
}
  :
  ( a=NUMBER
                                     { min = Integer.valueOf($a.text); }
    | r=range
                                     { min = $r.a; max = $r.b; }
    | c=ID
                                     { columnName = $c.text; }
  )
                                     { $t.setFillFactor(new FillFactor(min, max, per)); }
  ('per'
    (b=NUMBER
                                     { $t.setFillFactor(new FillFactor(min, max, Integer.parseInt($b.text))); }
    )?
    t1=table
                                     { if (columnName != null) $t.setFillFactor(new FillFactor(sb.getColumn($t1.t, columnName)));
                                       $t.addDependency($t1.t); $t1.t.addDependent(t); }
    ('and' t1=table
                                     { $t.addDependency($t1.t); $t1.t.addDependent(t); }
    )*
  )?
  ;
  
column_filling_list [Table t]
@after
{
  sb.checkTable($t);
}
  :
  LEFT_PAREN
  (column_filling[$t] (COMMA column_filling[$t])*)?
  RIGHT_PAREN
  ;
  
column_filling [Table t]
  :
  c=ID
                              { Column col = sb.getColumn(t, $ID.text); }
  'as' d=data_constraint
                              { col.setDataConstraint($d.dc); }
  ;

data_constraint returns [DataConstraint dc]
@init
{
  boolean global = false;
}
  :
  // For ease of definition, multiple first/random/last pcs are allowed, the last one will be used in the end
  vc=value_constructor
                                                    { $dc = new DataConstraint($vc.vc); }
  ( 'with'
    ('global'
                                                    { global = true; }
    | 'per' t=table
                                                    { $dc.setPartialContext(sb.getTable($t.text)); }
    )?
  ( 'first' pc=partial_constraint[global]
                                                    { $dc.setPcOnFirst($pc.pc); }
  | ('random')? pc=partial_constraint[global]
                                                    { $dc.setPcOnRandom($pc.pc); }
  | 'last' pc=partial_constraint[global]
                                                    { $dc.setPcOnLast($pc.pc); }                              
  )
  ( COMMA 'first' pc=partial_constraint[global]
                                                    { $dc.setPcOnFirst($pc.pc); }
  | COMMA ('random')? pc=partial_constraint[global]
                                                    { $dc.setPcOnRandom($pc.pc); }
  | COMMA 'last' pc=partial_constraint[global]
                                                    { $dc.setPcOnLast($pc.pc); }
  )*
  )?
  ;

value_constructor returns [ValueConstructor vc]
  :
  (
    rs=random_a_string
                                        { vc = $rs.vc; }
  | rs=random_n_string
                                        { vc = $rs.vc; }
  | rc=concat
                                        { vc = $rc.vc; }
  | un=unique_number
                                        { vc = $un.vc; }
  | rn=random_number
                                        { vc = $rn.vc; }
  | da=date
                                        { vc = $da.vc; }
  | de=decimal
                                        { vc = $de.vc; }
  | c=constant
                                        { vc = $c.vc; }
  | rf=reference
                                        { vc = $rf.vc; }
  | dv=derived
                                        { vc = $dv.vc; }
  | 'null'
                                        { vc = new Nulls(); }
  )
  ;
  
range returns [int a, int b]
  :
  '[' na=NUMBER DOTDOT nb=NUMBER ']'
                                      { $a = Integer.parseInt($na.text); $b = Integer.parseInt($nb.text); }
  ;

/* ValueConstructor rules */

random_a_string returns [ValueConstructor vc]
  :
  'random'? 'a-string' r=range f=fix_part
                                                  { vc = new RandomStrings($r.a, $r.b, false, $f.begin, $f.random, $f.end); }
  ;

random_n_string returns [ValueConstructor vc]
  :
  'random'? 'n-string' r=range f=fix_part         
                                                  { vc = new RandomStrings($r.a, $r.b, true, $f.begin, $f.random, $f.end); }
  ;

concat returns [ValueConstructor vc]
@init
{
  int random = -1;
}
  :
  ('random'
                                                  { random = 0; }
  | 'nuRandom' LEFT_PAREN n=NUMBER RIGHT_PAREN
                                                  { random = Integer.parseInt($n.text); }  
  )?
  
  'concat' r=range 'of' sy=syllables f=fix_part
                                                  { vc = new RandomStringConcats(random, $r.a, $r.b,
                                                        $sy.sa, $f.begin, $f.random, $f.end); }
  ;
 
unique_number returns [ValueConstructor vc]
@init
{
  boolean random = false;
  boolean global = false;
  int startFrom = 1;
  Table uniqueContext = null;
}
  :
    ('random'
                                                  { random = true; }
    )?
    ('global'
                                                  { global = true; }
    )?
    'unique' 'number'
    ('per' t=table
                                                  { uniqueContext = $t.t; }
    )?
    ('starting' 'from' n=NUMBER
                                                  { startFrom = Integer.parseInt($n.text); }
    )?
                                                  { vc = new UniqueNumbers(random, global, startFrom, uniqueContext); }
  ;

random_number returns [ValueConstructor vc]
  :
  'random'? 'integer' 'within' r=range
                                                  { vc = new RandomNumbers($r.a, $r.b); }
  ;
  
constant returns [ValueConstructor vc]
  :
  s=q_string
                                                  { vc = new Constants($s.s); }
  ;
 
reference returns [ValueConstructor vc]
@init
{
  boolean random = false;
  boolean unique = false;
  boolean dependent = false;
  int startFrom = 1;
  Table refContext = null;
}
  :
  ( 'random'
                                             { random = true; }
  )?
  ( 'dependent' 'reference'
                                             { dependent = true; }
  |
	  ('unique'
                                             { unique = true; }
	  )?
	  'reference'
	  ('starting' 'from' 'tuple' n=NUMBER
	                                           { startFrom = Integer.parseInt($n.text); }
	  )?
  )
  ('per' t=table
                                             { refContext = $t.t; }
  )?
                                             { vc = new References(random, dependent, unique, startFrom, $t.t); }
  ;

date returns [ValueConstructor vc]
  :
  ( 'current' 'date'
                                                  { vc = new Dates(); }
  | 'date' LEFT_PAREN d1=DATESTRING RIGHT_PAREN
                                                  { vc = new Dates($d1.text + " 00:00:00"); } 
  |
    'random'? 'date' 'within' '['
    d1=DATESTRING DOTDOT d2=DATESTRING
    ']'
                                                  { vc = new Dates($d1.text + " 00:00:00", $d2.text + " 23:59:59"); }
  )
  ;

decimal returns [ValueConstructor vc]
  :
  'random'? 'decimal' 'within'
  '['
  n1=NUMBER DOT n2=NUMBER
  DOTDOT
  n3=NUMBER DOT n4=NUMBER
  ']'
                                      { vc = new Decimals($n1.text + '.' + $n2.text, $n3.text + '.' + $n4.text); }
  ;
  
derived returns [ValueConstructor vc]
  :
  'derived' 'from' t=ID DOT c=ID
                                      { vc = new DerivedValues(sb.getColumn(sb.getTable($t.text), $c.text)); }
  ;
  
q_string returns [String s]
  :
  s1=STRING
                          { $s = $s1.text.substring(1, $s1.text.length() - 1); }
  ;

syllables returns [String[\] sa]
  :
                              { List<String> sl = new ArrayList(); }
  LEFT_PAREN s=q_string
                              { sl.add($s.s); }
  (COMMA s=q_string
                              { sl.add($s.s); }
  )*
  RIGHT_PAREN
                              { $sa = sl.toArray(new String[0]); }
  ;
 
// Cannot name first parameter 'start' as this will result in a conflict with internal fields in generated Parser class 
fix_part returns [String begin, String random, String end]
  :
  ('starting' 'with' s=q_string   
                                  { $begin = $s.s; }
  )?
  ('ending' 'with' s=q_string
                                  { $end = $s.s; }
  )?
  ('including' s=q_string
                                  { $random = $s.s; }
  )?
  ;

partial_constraint [boolean global] returns [PartialConstraint pc]

  :
  n=NUMBER PERCENT
                              { int percent = Integer.parseInt($n.text); }
  'as' vc=value_constructor
                              { $pc = new PartialConstraint(percent, $global,$vc.vc); }
  ;
  
/*------------------------------------------------------------------
 * LEXER RULES
 *------------------------------------------------------------------*/

LEFT_PAREN : '(';
RIGHT_PAREN : ')';
COMMA : ',';
SEMICOLON : ';';
DOT : '.';
NUMBER  : (DIGIT)+;
DATESTRING: (DIGIT | '-' | '_' | ':')+;
ID  : (('a'..'z'|'A'..'Z' | '_') ((DIGIT)*))+ ;
NEWLINE: '\r'? '\n' { $channel = HIDDEN; } ;
WS : ( '\t' | ' ' | '\r' | '\n' | '\u000C' )+ { $channel = HIDDEN; } ;
fragment DIGIT :   '0'..'9' ;

// Additional lexer rules to parse TPC-C like data generation constraints 

PERCENT : '%';
DOTDOT : '..';
QUOTE : '"';
STRING : QUOTE ('a'..'z'|'A'..'Z' | '-' | '_' | DIGIT | DOT | ':')+ QUOTE;
MULTI_COMMENT options { greedy = false; } : '/*' .* '*/' NEWLINE? { skip(); };